using System.CodeDom;
using System.Reflection;
using Castle.ActiveRecord;
using ModelGenerator.RepositoryGenerator.Factories;
using ModelGenerator.RepositoryGenerator.Strategies;

namespace ModelGenerator.RepositoryGenerator.Builders
{
    public class FunctionBuilder
    {
        /// <summary>
        /// Creates the function.
        /// </summary>
        /// <param name="classCodeBase">The code base.</param>
        /// <param name="interfaceCodeBase">The interface code base.</param>
        /// <param name="info">The info.</param>
        /// <param name="DomainClass">The domain class.</param>
        public static void CreateFunctions(CodeCompileUnit classCodeBase, CodeCompileUnit interfaceCodeBase,
                                           PropertyInfo info, string DomainClass)
        {
            var attributes = info.GetCustomAttributes(typeof (PropertyAttribute), false) as PropertyAttribute[];
            if (attributes.Length == 0)
                return;

            string propertyName = info.Name;
            string propertyType = info.PropertyType.Name;
            var isUniqueProperty = IsUniqueResult(attributes);

            // Used to place the regions
            int BeforeAddingMethodsCount = classCodeBase.Namespaces[0].Types[0].Members.Count;

            AddEqualMethod(classCodeBase, interfaceCodeBase, propertyName, propertyType, DomainClass, isUniqueProperty);
            AddLikeMethods(classCodeBase, interfaceCodeBase, propertyName, propertyType, DomainClass, isUniqueProperty);
            AddBetweenMethod(classCodeBase, interfaceCodeBase, propertyName, propertyType, DomainClass, isUniqueProperty);
            AddComparitionMethods(classCodeBase, interfaceCodeBase, propertyName, propertyType, DomainClass,
                                  isUniqueProperty);

            AddRegions(classCodeBase, interfaceCodeBase, propertyName, BeforeAddingMethodsCount);
        }

        /// <summary>
        /// Adds the equal method.
        /// </summary>
        /// <param name="classCodeBase">The class code base.</param>
        /// <param name="interfaceCodeBase">The interface code base.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="propertyType">Type of the property.</param>
        /// <param name="DomainClass">The domain class.</param>
        /// <param name="isUniqueProperty">if set to <c>true</c> [is unique property].</param>
        private static void AddEqualMethod(CodeCompileUnit classCodeBase, CodeCompileUnit interfaceCodeBase,
                                           string propertyName, string propertyType, string DomainClass,
                                           bool isUniqueProperty)
        {
            AddMethod(classCodeBase,
                      EqualMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty, true));
            AddMethod(interfaceCodeBase,
                      EqualMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty, false));
        }

        /// <summary>
        /// Adds the like methods.
        /// </summary>
        /// <param name="classCodeBase">The class code base.</param>
        /// <param name="interfaceCodeBase">The interface code base.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="propertyType">Type of the property.</param>
        /// <param name="DomainClass">The domain class.</param>
        /// <param name="isUniqueProperty">if set to <c>true</c> [is unique property].</param>
        private static void AddLikeMethods(CodeCompileUnit classCodeBase, CodeCompileUnit interfaceCodeBase,
                                           string propertyName, string propertyType, string DomainClass,
                                           bool isUniqueProperty)
        {
            AddMethod(classCodeBase,
                      LikeMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                     new LikeModeStrategy(), true));
            AddMethod(interfaceCodeBase,
                      LikeMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                     new LikeModeStrategy(), false));

            AddMethod(classCodeBase,
                      LikeMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                     new StartWithModeStrategy(), true));
            AddMethod(interfaceCodeBase,
                      LikeMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                     new StartWithModeStrategy(), false));

            AddMethod(classCodeBase,
                      LikeMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                     new EndWithModeStrategy(), true));
            AddMethod(interfaceCodeBase,
                      LikeMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                     new EndWithModeStrategy(), false));
        }

        /// <summary>
        /// Adds the between method.
        /// </summary>
        /// <param name="classCodeBase">The class code base.</param>
        /// <param name="interfaceCodeBase">The interface code base.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="propertyType">Type of the property.</param>
        /// <param name="DomainClass">The domain class.</param>
        /// <param name="isUniqueProperty">if set to <c>true</c> [is unique property].</param>
        private static void AddBetweenMethod(CodeCompileUnit classCodeBase, CodeCompileUnit interfaceCodeBase,
                                             string propertyName, string propertyType, string DomainClass,
                                             bool isUniqueProperty)
        {
            AddMethod(classCodeBase,
                      BetweenMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty, true));
            AddMethod(interfaceCodeBase,
                      BetweenMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty, false));
        }

        /// <summary>
        /// Adds the comparition methods.
        /// </summary>
        /// <param name="classCodeBase">The class code base.</param>
        /// <param name="interfaceCodeBase">The interface code base.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="propertyType">Type of the property.</param>
        /// <param name="DomainClass">The domain class.</param>
        /// <param name="isUniqueProperty">if set to <c>true</c> [is unique property].</param>
        private static void AddComparitionMethods(CodeCompileUnit classCodeBase, CodeCompileUnit interfaceCodeBase,
                                                  string propertyName, string propertyType, string DomainClass,
                                                  bool isUniqueProperty)
        {
            AddMethod(classCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new GreaterStrategy(), true));
            AddMethod(interfaceCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new GreaterStrategy(), false));

            AddMethod(classCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new GreaterOrEqualStrategy(), true));
            AddMethod(interfaceCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new GreaterOrEqualStrategy(), false));

            AddMethod(classCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new LowerStrategy(), true));
            AddMethod(interfaceCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new LowerStrategy(), false));

            AddMethod(classCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new LowerEqualStrategy(), true));
            AddMethod(interfaceCodeBase,
                      CompareMethodFactory.CreateMethod(propertyName, propertyType, DomainClass, isUniqueProperty,
                                                        new LowerEqualStrategy(), false));
        }

        /// <summary>
        /// Adds the regions.
        /// </summary>
        /// <param name="classCodeBase">The code base.</param>
        /// <param name="interfaceCodeBase">The interface code base.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="BeforeAddingMethodsCount">The before adding methods count.</param>
        private static void AddRegions(CodeCompileUnit classCodeBase, CodeCompileUnit interfaceCodeBase,
                                       string propertyName, int BeforeAddingMethodsCount)
        {
            classCodeBase.Namespaces[0].Types[0].Members[BeforeAddingMethodsCount].StartDirectives.Add(
                new CodeRegionDirective(CodeRegionMode.Start, string.Format("{0} methods", propertyName)));
            interfaceCodeBase.Namespaces[0].Types[0].Members[BeforeAddingMethodsCount].StartDirectives.Add(
                new CodeRegionDirective(CodeRegionMode.Start, string.Format("{0} methods", propertyName)));

            var methodCount = classCodeBase.Namespaces[0].Types[0].Members.Count;
            classCodeBase.Namespaces[0].Types[0].Members[methodCount - 1].EndDirectives.Add(
                new CodeRegionDirective(CodeRegionMode.End, ""));
            interfaceCodeBase.Namespaces[0].Types[0].Members[methodCount - 1].EndDirectives.Add(
                new CodeRegionDirective(CodeRegionMode.End, ""));
        }

        /// <summary>
        /// Adds the method.
        /// </summary>
        /// <param name="codeBase">The code base.</param>
        /// <param name="method">The method.</param>
        private static void AddMethod(CodeCompileUnit codeBase, CodeMemberMethod method)
        {
            if (method != null)
            {
                method.Attributes = MemberAttributes.Public;
                codeBase.Namespaces[0].Types[0].Members.Add(method);
            }
        }

        /// <summary>
        /// Determines whether [is unique result] [the specified attributes].
        /// </summary>
        /// <param name="attributes">The attributes.</param>
        /// <returns>
        /// 	<c>true</c> if [is unique result] [the specified attributes]; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsUniqueResult(PropertyAttribute[] attributes)
        {
            return attributes[0].Unique;
        }
    }
}